import axios, { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
import { ComponentInternalInstance, getCurrentInstance } from 'vue'
import { StatesUtils, StatesUtils as states } from './states'
import log from './Log'
import PathUtils from './PathUtils'
import pathUtils from './PathUtils'
import { AxiosProgressEvent } from 'axios/index'


const getRequest=(url:string):AxiosInstance=>{
    const urlObj=new URL(url)
    const config=getHostConfig(urlObj.host)
    const request= axios.create({timeout:config?.timeout})
    request.interceptors.request.use(requestHandle)
    request.interceptors.response.use(responseHandle)
    return request
}

const request=axios.create({
    timeout:3000
})


export const DEFAULT_NEED_LOGIN_CODE=510

export type Result<T=any>={
    code:number
    data?:T
    msg:string
}

export type ErrorListener=(error:Error)=>void

export type HostCallback =(host:string)=>void

export interface HostConfig {
    host:string
    tokenKey?:'token'
    timeout?:4000
    token?:string
    successCode:200
    needLoginCode: 510
}

const defaultConfig:HostConfig={
    host:'127.0.0.1',
    tokenKey:'token',
    timeout:4000,
    successCode:200,
    needLoginCode: 510
}

const responseHandle=(value:AxiosResponse<any,any>)=>{
    if (value.status===200){
        log.info(value)
        log.info("response url:",value.config.url)

        let urlInfo:URL=new URL(value.config.url!)
        let host=urlInfo.host
        let tokenKeyName='token'
        let needLoginCode=DEFAULT_NEED_LOGIN_CODE

        if (hostConfigs.has(urlInfo.host)){
            tokenKeyName=hostConfigs.get(urlInfo.host)!.tokenKey!
            needLoginCode=hostConfigs.get(urlInfo.host)!.needLoginCode
        }

        if (value.data["code"]&&value.data["code"]===needLoginCode){
            doNeedLogin(urlInfo.host)
            return Promise.reject(new Error(value.data["msg"]))
        }

        if (value.data["code"]&&value.data["code"]===200){
            let token=value.headers[tokenKeyName]
            if (token){
                StatesUtils.setState(host+':token',token)
            }
            return Promise.resolve(value)
        }

        if (value.data["msg"]) {
            return Promise.reject(new Error(value.data["msg"]))
        }
    }
    else {
        Promise.reject(new Error(value.statusText))
    }
    return value
}

const requestHandle=(value:InternalAxiosRequestConfig<any>):InternalAxiosRequestConfig<any>=>{
    if (value.url){
        value.withCredentials=true
        let urlInfo=new URL(value.url?value.url:value.baseURL!)
        let host=urlInfo.host
        log.info("request host:",host)
        let tokenKeyName="token"

        if (hostConfigs.has(host)){
            let config=hostConfigs.get(host)
            tokenKeyName=config?.tokenKey?config.tokenKey:tokenKeyName
            value.timeout=config?.timeout
        }

        let key=host+":token"
        if (states.has(key)){
            value.headers.set(tokenKeyName,states.getState(key))
            log.info("set head token",tokenKeyName,states.getState(key))
        }
    }
    return value
}

const hostConfigs=new Map<string,HostConfig>()

const setHostConfig=(config:HostConfig):void=> {
    hostConfigs.set(config.host,config)
}

const getHostConfig=(host:string):HostConfig|undefined=>{
    return hostConfigs.get(host)
}

const getDefaultHostConfig=():HostConfig=>{return defaultConfig}

const errorListenerList=new Map<ComponentInternalInstance,ErrorListener>()

const watchError=(listener:ErrorListener)=>{
    let instance=getCurrentInstance()
    if (instance&&!errorListenerList.has(instance)){
        errorListenerList.set(instance,listener)
    }
}

const doError=(error:Error)=>{
    if (errorListenerList.size>0){
        errorListenerList.forEach((
            value:ErrorListener,
            key:ComponentInternalInstance,
            map:Map<ComponentInternalInstance,ErrorListener>
        )=>{
            if (!key.isUnmounted){
                try{
                    value(error)
                }
                catch(e){
                    console.error(e)
                }
            }
            else
            {
                map.delete(key)
            }
        })
        return
    }
    console.error(error)
}

const needLoginListenerList:Array<HostCallback>=[]
const doNeedLogin=(host:string)=>{
    needLoginListenerList.forEach((callback)=>{
        callback(host)
    })
}

const setNeedLoginCallback=(callback:HostCallback)=>{
    if (!needLoginListenerList.findLast((e)=>{return e===callback})) {
        needLoginListenerList.push(callback)
    }
}

request.interceptors.response.use(responseHandle)
request.interceptors.request.use(requestHandle)

export type Success<T=any>=(result:Result<T>,url?:string)=>void
export type Fail=(error:Error)=>void

const parameterToUrl=(baseUrl:string,parameter?:any):string=>{
    let result=baseUrl.indexOf('?')>=0?baseUrl:baseUrl+"?"
    if (parameter){
        let paramStr=""
        for(let key in parameter){
            let pathVar=`{${key}}`
            if (result.includes(key,0)) {
                result = result.replace(pathVar, parameter[key])
            }
            else {
                paramStr += "&" + key + "=" + parameter[key]
            }
        }
        result=result+PathUtils.formatByChar(paramStr,"&")
    }
    return result
}

let DefaultHost: string = 'http://127.0.0.1'

const setDefaultHost=(host:string)=>{
    DefaultHost=host
}

const getDefaultHost=()=>DefaultHost

const haveHost=(url:string):boolean=>{
    return url.startsWith("http://") || url.startsWith("https://");
}

const formatUrl=(url:string):string=>{
    if (haveHost(url)) return url
    return pathUtils.comboPath(getDefaultHost(),url)
}

const setHostToken=(hostOrUrl:string,token:string)=>{
    log.info("set host token",hostOrUrl,token)
    if (!hostOrUrl){
        log.warn("hostOrUrl is null or undefined!")
    }
    let realHost=new URL(hostOrUrl).host
    StatesUtils.setState(realHost+":token",token)
}

interface HttpCallback {
    success?:Success
    fail?:Fail
    complete?:() => void
    process?:(process:number)=>void
}


class Http {

    post(action:string,data?:any,success?:Success,fail?:Fail){
        let strUrl=formatUrl(action)
       return getRequest(strUrl).post(strUrl,data)
       .then((resp:AxiosResponse)=>{
            if (success){
                success(resp.data,resp.config.url)
            }
       })
       .catch((error:Error)=>{
            if (fail){
                fail(error)
            }
            else{
                doError(error)
            }
       })
    }

    postAsCallback(action:string,data:any,callback?:HttpCallback){
        let strUrl=formatUrl(action)
        return getRequest(strUrl).post(strUrl,data)
          .then((resp:AxiosResponse)=>{
              if (callback&&callback.complete){
                  callback!.complete()
              }
              if (callback&&callback.success){
                  callback!.success(resp.data,resp.config.url)
              }
          })
          .catch((error:Error)=>{
              if (callback&&callback.complete){
                  callback!.complete()
              }
              if (callback&&callback.fail){
                  callback!.fail(error)
              }
              else{
                  doError(error)
              }
          })
    }

    postForm(action:string,data?:FormData,success?:Success,fail?:Fail):void{
        let strUrl=formatUrl(action)
        getRequest(strUrl).postForm(strUrl,data)
        .then((resp:AxiosResponse)=>{
            if (success){
                success(resp.data)
            }
        })
        .catch((error:Error)=>{
            if (fail){
                fail(error)
            }
            else{
                doError(error)
            }
        })
    }

    postFormAsCallback(action:string,data?:FormData,callback?:HttpCallback):void{
        let strUrl=formatUrl(action)
        getRequest(strUrl)
          .postForm(strUrl,data,{onUploadProgress:(progressEvent: AxiosProgressEvent)=>{
                if (callback&&callback.process){
                    callback.process(progressEvent.progress)
                }
              }})
          .then((resp:AxiosResponse)=>{
              if (callback&&callback.complete){
                  callback!.complete()
              }
              if (callback&&callback.success){
                  callback!.success(resp.data)
              }
          })
          .catch((error:Error)=>{
              if (callback&&callback.complete){
                  callback!.complete()
              }
              if (callback&&callback.fail){
                  callback!.fail(error)
              }
              else{
                  doError(error)
              }
          })
    }

    get(action:string,parameters?:any,success?:Success,fail?:Fail):void{
        let strUrl=formatUrl(action)
        getRequest(strUrl).get(parameterToUrl(strUrl,parameters))
        .then((resp:AxiosResponse)=>{
            if (success){
                success(resp.data)
            }
        })
        .catch((error:Error)=>{
            fail?fail(error):doError(error)
        })
    }

    getAsCallback(action:string,parameters?:any,callback?:HttpCallback):void{
        let strUrl=formatUrl(action)
        getRequest(strUrl).get(parameterToUrl(strUrl,parameters))
          .then((resp:AxiosResponse)=>{
              if (callback&&callback.complete){
                  callback!.complete()
              }
              if (callback&&callback.success){
                  callback.success(resp.data)
              }
          })
          .catch((error:Error)=>{
              if (callback&&callback.complete){
                  callback!.complete()
              }
              if (callback&&callback?.fail){
                  callback!!.fail(error)
              }
              else{
                  doError(error)
              }
          })
    }

    async asyncPost<T>(action:string,data?:unknown):Promise<Result<T>>{

        return new Promise(async (resolve, reject)=>{
            try {
                let strUrl=formatUrl(action)
                let result = await getRequest(strUrl).post(strUrl, data)
                let tResult= result.data as Result<T>
                resolve(tResult)
            }
            catch (error) {
                if (error instanceof Error){
                    doError(error)
                }
                reject(error)
            }
        })
    }

    async asyncGet<T>(action:string,parameters?:unknown):Promise<Result<T>>{
        return new Promise(async (resolve, reject)=>{
            try {
                let strUrl=formatUrl(action)
                let result =await getRequest(strUrl).get(parameterToUrl(strUrl,parameters))
                let tResult=result.data as Result<T>
                resolve(tResult)
            }
            catch (error) {
                if (error instanceof Error) {
                    doError(error)
                }
                reject(error)
            }
        })
    }

    async asyncPostForm<T>(action:string,data?:FormData):Promise<Result<T>>{
        return new Promise(async (resolve, reject)=>{
            try {
                let strUrl=formatUrl(action)
                let result =await getRequest(strUrl).postForm(strUrl,data)
                let tResult=result.data as Result<T>
                resolve(tResult)
            }
            catch (error) {
                if (error instanceof Error) {
                    doError(error)
                }
                reject(error)
            }
        })
    }

}

export const http=new Http()
export { setDefaultHost, getDefaultHost, setHostToken, setHostConfig, watchError, setNeedLoginCallback, getHostConfig, getDefaultHostConfig }
export default request
